from datetime import datetime, timedelta
from io import BytesIO
import json
import os
from pipes import quote
import uuid
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.shortcuts import render, redirect
import requests
from polls.models import Subject, SubjectMapper, SubjectSerializer, SubjectSimpleSerializer, Teacher, TeacherSerializer, TeacherSerializerAll, User
from utils import Captcha, check_tel, gen_md5_digest, gen_random_code, random_code, upload_stream_to_qiniu
import xlwt
from rest_framework.decorators import api_view,permission_classes
from rest_framework.response import Response
import jwt
from rest_framework.permissions import IsAuthenticated,AllowAny
from rest_framework.views import APIView
from django.contrib.auth import authenticate, login as auth_login, logout as auth_logout
from rest_framework_simplejwt.tokens import RefreshToken,AccessToken

# 缓存redis的使用
from django.views.decorators.cache import cache_page
from django_redis import get_redis_connection


@api_view(('GET', ))
# @cache_page(timeout=86400, cache='default') #缓存装饰器，只能用于函数视图（FVB），但是不够灵活，推荐编程式
def api_subjects_caches(request):
    """获取学科数据"""
    redis_cli = get_redis_connection()
    # 先尝试从缓存中获取学科数据
    data = redis_cli.get('vote:polls:subjects')
    if data:
        # 如果获取到学科数据就进行反序列化操作
        data = json.loads(data)
    else:
        # 如果缓存中没有获取到学科数据就查询数据库
        queryset = Subject.objects.all()
        data = SubjectSerializer(queryset, many=True).data
        # 将查到的学科数据序列化后放到缓存中
        redis_cli.set('vote:polls:subjects', json.dumps(data), ex=86400)
    return Response({'code': 200, 'subjects': data})
    """获取学科数据"""
    """
    queryset = Subject.objects.all()
    data = SubjectSerializer(queryset, many=True).data
    return Response({'code': 20000, 'subjects': data})
    """

def show_subjects(request):
    
    # from utils import create_jwt,decode_jwt
    # print("测试token生成："+create_jwt("SNO001","admin"))
    # print(f"测试token解码：{decode_jwt(create_jwt("SNO001","admin"))}")
    queryset = Subject.objects.all()
    subjects = []
    for subject in queryset:
        subjects.append(SubjectMapper(subject).as_dict())
    if request.headers.get('X-Requested-With') == 'XMLHttpRequest' or request.method == 'POST':
        return JsonResponse(subjects, safe=False)
    else:
        return render(request,'../templates/subjects.html',{"subjects":subjects})
# 基于函数的视图 FBV
def show_subjects_api(request):
    queryset = Subject.objects.all()
    subjects = []
    for subject in queryset:
        subjects.append(SubjectMapper(subject).as_dict())
    return JsonResponse(subjects, safe=False)
# 基于类的视图 CBV
from rest_framework.generics import ListAPIView
class SubjectView(ListAPIView):
    # permission_classes=[AllowAny] #无需鉴权即可响应
    # 通过queryset指定如何获取学科数据
    queryset = Subject.objects.all()
    # 通过serializer_class指定如何序列化学科数据
    serializer_class = SubjectSerializer
# 基于CBV，并且支持GET、POST、PUT、PATCH、DELETE请求
from rest_framework.viewsets import ModelViewSet
class SubjectViewSet(ModelViewSet):
    queryset = Subject.objects.all()
    serializer_class = SubjectSerializer
    
def show_teachers(request):
    try:
        sno = int(request.GET.get('sno') or 1)
        teachers = []
        if sno:
            subject = Subject.objects.only('name').get(no=sno)
            teachers = Teacher.objects.filter(subject=subject).order_by('no')
        return render(request, '../templates/teachers.html', {
            'subject': subject,
            'teachers': teachers
        })
    except (ValueError, Subject.DoesNotExist):
        return redirect('/')

def praise_or_criticize(request: HttpRequest) -> HttpResponse:
    if request.session.get('userid'):
        try:
            tno = int(request.GET.get('tno'))
            teacher = Teacher.objects.get(no=tno)
            if request.path.startswith('/praise/'):
                teacher.good_count += 1
                count = teacher.good_count
            else:
                teacher.bad_count += 1
                count = teacher.bad_count
            teacher.save()
            data = {'code': 20000, 'mesg': '投票成功', 'count': count}
        except (ValueError, Teacher.DoesNotExist):
            data = {'code': 20001, 'mesg': '投票失败'}
    else:
        data = {'code': 20002, 'mesg': '请先登录'}
    return JsonResponse(data)

def login(request: HttpRequest) -> HttpResponse:
    #数据库2个账号密码:wangdachui  1qaz2wsx
    #                hellokitty     Abc123!!
    hint = ''
    if request.method == 'POST':
        if request.session.test_cookie_worked():
            request.session.delete_test_cookie()
            # Add your code to perform login process here
        else:
            return render(request, '../templates/login.html', {'hint': "Please enable cookies and try again."})
    request.session.set_test_cookie()
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        captcha = request.POST.get('captcha')
        #判断验证码
        if request.session['captcha']==captcha:
            if username and password:
                password = gen_md5_digest(password)
                user = User.objects.filter(username=username, password=password).first()
                # 使用 Django 的 authenticate 函数；这方法需要改创建视图；
                # 因为这是django自带的，会把密码哈希再存，不需要自己md5加密；暂不使用
                # user = authenticate(request, username=username, password=password)
                if user:
                    request.session['userid'] = user.no
                    request.session['username'] = user.username
                    # 注意，jwt默认的user的主键是id，而现在是no，需要去settings修改
                    # auth_login(request, user)  # 使用Django内置的认证系统登录
                    refresh = RefreshToken.for_user(user)  # 创建JWT令牌
                
                    # return redirect('/')
                    responseJson=JsonResponse({
                        'access': str(refresh.access_token),
                        'refresh': str(refresh),
                        'redirect_url':'/'
                    })
                    # print("即将返回这个值：",responseJson)
                    return responseJson
                else:
                    hint = '用户名或密码错误'
            else:
                hint = '请输入有效的用户名和密码'
        else:
            hint = '验证码错误！(看不清就去看后端控制台)'
    return render(request, '../templates/login.html', {'hint': hint})

def logout(request):
    # auth_logout(request)
    """注销"""
    request.session.flush()
    return redirect('/')
def test(request):        
    return render(request, '../templates/test.html')
def get_captcha(request: HttpRequest) -> HttpResponse:
    """验证码"""
    captcha_text = gen_random_code()
    request.session['captcha'] = captcha_text
    print("验证码：",captcha_text) #不打印，我都看不清验证码
    image_data = Captcha.instance().generate(captcha_text)
    return HttpResponse(image_data, content_type='image/png')

def export_teachers_excel(request):
    # 创建工作簿
    wb = xlwt.Workbook()
    # 添加工作表
    sheet = wb.add_sheet('老师信息表')
    # 查询所有老师的信息
    # queryset = Teacher.objects.all()
    queryset = Teacher.objects.all().select_related('subject')
    # 向Excel表单中写入表头
    colnames = ('姓名', '介绍', '好评数', '差评数', '学科')
    for index, name in enumerate(colnames):
        sheet.write(0, index, name)
    # 向单元格中写入老师的数据
    props = ('name', 'intro', 'good_count', 'bad_count', 'subject')
    for row, teacher in enumerate(queryset):
        for col, prop in enumerate(props):
            value = getattr(teacher, prop, '')
            if isinstance(value, Subject):
                value = value.name
            sheet.write(row + 1, col, value)
    # 保存Excel
    buffer = BytesIO()
    wb.save(buffer)
    # 将二进制数据写入响应的消息体中并设置MIME类型
    resp = HttpResponse(buffer.getvalue(), content_type='application/vnd.ms-excel')
    # 中文文件名需要处理成百分号编码
    filename = quote('老师.xls')
    # 通过响应头告知浏览器下载该文件以及对应的文件名
    resp['content-disposition'] = f'attachment; filename*=utf-8\'\'{filename}'
    return resp

def get_teachers_data(request):
    # queryset = Teacher.objects.all()
    queryset = Teacher.objects.all().only('name', 'good_count', 'bad_count')
    names = [teacher.name for teacher in queryset]
    good_counts = [teacher.good_count for teacher in queryset]
    bad_counts = [teacher.bad_count for teacher in queryset]
    return render(request, '../templates/teachers_data.html', {'names': names, 'good': good_counts, 'bad': bad_counts})


@api_view(('GET', ))
def api_subjects(request: HttpRequest) -> HttpResponse:
    subjects = Subject.objects.all().order_by('no')
    # 创建序列化器对象并指定要序列化的模型
    serializer = SubjectSerializer(subjects, many=True)
    # 通过序列化器的data属性获得模型对应的字典并通过创建Response对象返回JSON格式的数据
    return Response(serializer.data)

@api_view(('GET', ))
# @permission_classes([IsAuthenticated])
def api_teachers(request: HttpRequest) -> HttpResponse:
    try:
        sno = int(request.GET.get('sno'))
        subject = Subject.objects.only('name').get(no=sno)
        teachers = Teacher.objects.filter(subject=subject).defer('subject').order_by('no')
        subject_seri = SubjectSimpleSerializer(subject)
        teacher_seri = TeacherSerializer(teachers, many=True)
        return Response({'subject': subject_seri.data, 'teachers': teacher_seri.data})
    except (TypeError, ValueError, Subject.DoesNotExist):
        return Response(status=404)
    
@api_view(('GET', ))
@permission_classes([IsAuthenticated])
def api_teachers_all(request: HttpRequest) -> HttpResponse:
    try:
        teachers_all=Teacher.objects.all()
        teacher_all_seri = TeacherSerializerAll(teachers_all, many=True)
        return Response({"teachers_all":teacher_all_seri.data})
    except (TypeError, ValueError, Teacher.DoesNotExist):
        return Response(status=404)

#螺丝帽官网发送短信验证码

""" 直接测试，没问题
@api_view(('GET', ))
def send_mobile_code(request):
    tel=request.GET.get("tel")
    code=request.GET.get("code")
    # 发送短信验证码
    resp = requests.post(
        url='http://sms-api.luosimao.com/v1/send.json',
        auth=('api', 'key-283bb19d3331c718ea2bab0f00ed40dd'),
        data={
            'mobile': tel,
            'message': f'您的短信验证码是{code}；我是布洛特亨德尔！【铁壳测试】'
        },
        verify=False
    )
    return Response(resp.json())
"""

def send_mobile_code(tel,code):
    # 发送短信验证码
    resp = requests.post(
        url='http://sms-api.luosimao.com/v1/send.json',
        auth=('api', 'key-283bb19d3331c718ea2bab0f00ed40dd'),
        data={
            'mobile': tel,
            'message': f'您的短信验证码是{code}；我是布洛特亨德尔！【铁壳测试】'
        },
        verify=False
    )
    return resp.json()

#给螺丝帽官网发送请求
@api_view(('GET', ))
def get_mobilecode(request):
    tel=request.GET.get("tel")
    """获取短信验证码"""
    if check_tel(tel):
        # redis缓存这个手机号
        redis_cli = get_redis_connection()
        if redis_cli.exists(f'vote:block-mobile:{tel}'):
            data = {'code': 30001, 'message': '请不要在60秒内重复发送短信验证码'}
        else:
            code = random_code()
            sendCodeResult=send_mobile_code(tel, code)
            # 通过Redis阻止60秒内容重复发送短信验证码
            redis_cli.set(f'vote:block-mobile:{tel}', 'x', ex=60)
            # 将验证码在Redis中保留10分钟（有效期10分钟）
            redis_cli.set(f'vote2:valid-mobile:{tel}', code, ex=600)
            data = {'code': 30000, 'message': '短信验证码已发送，请注意查收',"sendCodeResult":sendCodeResult}
    else:
        data = {'code': 30002, 'message': '请输入有效的手机号'}
    return Response(data)

from django.views.decorators.csrf import csrf_exempt

#上传文件到七牛
@csrf_exempt #该装饰器能够让表单免除必须提供CSRF令牌的要求
def uploadToQiNiu(request):# 检查是否提供了文件
    if request.method=="GET":
        return render(request, '../templates/uploadToQiNiu.html')
    # 获取上传的文件
    photo = request.FILES.get('photo')
    if photo is None:
        return HttpResponse("没有选择文件", status=400)
    # 如果上传的文件小于2.5M，则photo对象的类型为InMemoryUploadedFile，文件在内存中
    # 如果上传的文件超过2.5M，则photo对象的类型为TemporaryUploadedFile，文件在临时路径下
    photo = request.FILES.get('photo')
    _, ext = os.path.splitext(photo.name)
    # 通过UUID和原来文件的扩展名生成独一无二的新的文件名
    filename = f'{uuid.uuid1().hex}{ext}'
    # 对于内存中的文件，可以使用上面封装好的函数upload_stream_to_qiniu上传文件到七牛云
    # 如果文件保存在临时路径下，可以使用upload_file_to_qiniu实现文件上传
    upload_stream_to_qiniu(filename, photo.file, photo.size)
    return JsonResponse({"msg":"上传成功！"})